//-----------------------------------------------------------------------------
// GameScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
using System;
using System.ComponentModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Ferpect.GameComponents;
using Ferpect.GameComponents.Design;

namespace Ferpect.GameState
{
    /// <summary>
    /// A screen is a single layer that has update and draw logic, and which
    /// can be combined with other layers to build up a complex menu system.
    /// For instance the main menu, the options menu, the "are you sure you
    /// want to quit" message box, and the main game itself are all implemented
    /// as screens.
    /// </summary>
    public abstract class GameScreen : ViewComponent
    {
        private bool isExiting;
        private bool isPopup;
        private TimeSpan transitionOnTime = TimeSpan.Zero;
        private TimeSpan transitionOffTime = TimeSpan.Zero;
        private float transitionPosition = 1;
        private ScreenState screenState = ScreenState.TransitionOn;
        private bool otherScreenHasFocus;
        private ScreenManager screenManager;
        private BackgroundScreen backgroundScreen;
        private string backgroundTextureName;
        private Texture2D backgroundTexture;

        #region Properties

        /// <summary>
        /// If this screen has its own background screen, then the ScreenManager
        /// will automatically add it when this screen is added, and remove it when
        /// this screen is removed (unless it is exiting).
        /// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public BackgroundScreen BackgroundScreen
        {
            get { return this.backgroundScreen; }
            set { backgroundScreen = value; }
        }

        [Category("Appearance")]
        [DefaultValue("")]
        [Description("A background texture to use in place of a BackgroundScreen. If a BackgroundScreen is set, this property will be ignored.")]
        [TypeConverter(typeof(Texture2DConverter))]
        public string BackgroundTexture
        {
            get { return backgroundTextureName; }
            set { backgroundTextureName = value; }
        }

        public override ContentManager Content
        {
            get
            {
                ContentManager baseContent = base.Content;
                if (baseContent == null && this.screenManager != null)
                {
                    return this.screenManager.Content;
                }
                return baseContent;
            }
        }
	
        /// <summary>
        /// Normally when one screen is brought up over the top of another,
        /// the first screen will transition off to make room for the new
        /// one. This property indicates whether the screen is only a small
        /// popup, in which case screens underneath it do not need to bother
        /// transitioning off.
        /// </summary>
        [Category("Screen")]
        [DefaultValue(false)]
        public bool IsPopup
        {
            get { return isPopup; }
            set { isPopup = value; }
        }

        [Category("Behavior")]
        [DefaultValue(0)]
        [Description("Indicates how long the screen takes to transition on when it is activated.")]
        public double TransitionOnSeconds
        {
            get { return this.transitionOnTime.TotalSeconds; }
            set
            {
                if (value < 0.0)
                {
                    throw new ArgumentOutOfRangeException("value");
                }
                this.transitionOnTime = TimeSpan.FromSeconds(value);
            }
        }
	
        [Category("Behavior")]
        [DefaultValue(0)]
        [Description("Indicates how long the screen takes to transition off when it is deactivated.")]
        public double TransitionOffSeconds
        {
            get { return this.transitionOffTime.TotalSeconds; }
            set
            {
                if (value < 0.0)
                {
                    throw new ArgumentOutOfRangeException("value");
                }
                this.transitionOffTime = TimeSpan.FromSeconds(value);
            }
        }

        /// <summary>
        /// Gets the current position of the screen transition, ranging
        /// from zero (fully active, no transition) to one (transitioned
        /// fully off to nothing).
        /// </summary>
        [Browsable(false)]
        public float TransitionPosition
        {
            get { return transitionPosition; }
            protected set { transitionPosition = value; }
        }

        /// <summary>
        /// Gets the current alpha of the screen transition, ranging
        /// from 255 (fully active, no transition) to 0 (transitioned
        /// fully off to nothing).
        /// </summary>
        [Browsable(false)]
        public byte TransitionAlpha
        {
            get { return (byte)(255 - TransitionPosition * 255); }
        }

        /// <summary>
        /// Gets the current screen transition state.
        /// </summary>
        [Browsable(false)]
        public ScreenState ScreenState
        {
            get { return screenState; }
            protected set { screenState = value; }
        }

        /// <summary>
        /// There are two possible reasons why a screen might be transitioning
        /// off. It could be temporarily going away to make room for another
        /// screen that is on top of it, or it could be going away for good.
        /// This property indicates whether the screen is exiting for real:
        /// if set, the screen will automatically remove itself as soon as the
        /// transition finishes.
        /// </summary>
        [Browsable(false)]
        public bool IsExiting
        {
            get { return isExiting; }
            protected internal set { isExiting = value; }
        }

        /// <summary>
        /// Checks whether this screen is active and can respond to user input.
        /// </summary>
        [Browsable(false)]
        public bool IsActive
        {
            get
            {
                return !otherScreenHasFocus &&
                       (screenState == ScreenState.TransitionOn ||
                        screenState == ScreenState.Active);
            }
        }

        /// <summary>
        /// Gets the manager that this screen belongs to.
        /// </summary>
        [Browsable(false)]
        public ScreenManager ScreenManager
        {
            get { return screenManager; }
            set { screenManager = value; }
        }

        #endregion

        #region Update and Draw

        protected override void LoadContent()
        {
            if (this.backgroundScreen == null && !String.IsNullOrEmpty(this.backgroundTextureName))
            {
                this.backgroundTexture = this.Content.Load<Texture2D>(this.backgroundTextureName);
            }
            base.LoadContent();
        }

        protected override void UnloadContent()
        {
            this.backgroundTexture = null;
            base.UnloadContent();
        }

        public override void Draw(GameTime gameTime)
        {
            if (this.backgroundTexture != null)
            {
                SpriteBatch spriteBatch = this.ScreenManager.SpriteBatch;
                Viewport viewport = this.ScreenManager.GraphicsDevice.Viewport;
                Rectangle fullscreen = new Rectangle(0, 0, viewport.Width, viewport.Height);
                byte fade = this.TransitionAlpha;

                spriteBatch.Begin(SpriteBlendMode.None);

                spriteBatch.Draw(backgroundTexture, fullscreen, new Color(fade, fade, fade));

                spriteBatch.End();
            }
        }
        /// <summary>
        /// Allows the screen to run logic, such as updating the transition position.
        /// Unlike HandleInput, this method is called regardless of whether the screen
        /// is active, hidden, or in the middle of a transition.
        /// </summary>
        public virtual void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            this.otherScreenHasFocus = otherScreenHasFocus;

            if (isExiting)
            {
                // If the screen is going away to die, it should transition off.
                screenState = ScreenState.TransitionOff;

                if (!UpdateTransition(gameTime, transitionOffTime, 1))
                {
                    // When the transition finishes, remove the screen.
                    ScreenManager.RemoveScreen(this);
                }
            }
            else if (coveredByOtherScreen)
            {
                // If the screen is covered by another, it should transition off.
                if (UpdateTransition(gameTime, transitionOffTime, 1))
                {
                    // Still busy transitioning.
                    screenState = ScreenState.TransitionOff;
                }
                else
                {
                    // Transition finished!
                    screenState = ScreenState.Hidden;
                }
            }
            else
            {
                // Otherwise the screen should transition on and become active.
                if (UpdateTransition(gameTime, transitionOnTime, -1))
                {
                    // Still busy transitioning.
                    screenState = ScreenState.TransitionOn;
                }
                else
                {
                    // Transition finished!
                    screenState = ScreenState.Active;
                }
            }
        }

        /// <summary>
        /// Helper for updating the screen transition position.
        /// </summary>
        bool UpdateTransition(GameTime gameTime, TimeSpan time, int direction)
        {
            // How much should we move by?
            float transitionDelta;

            if (time == TimeSpan.Zero)
                transitionDelta = 1;
            else
                transitionDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds / time.TotalMilliseconds);

            // Update the transition position.
            transitionPosition += transitionDelta * direction;

            // Did we reach the end of the transition?
            if ((transitionPosition <= 0) || (transitionPosition >= 1))
            {
                transitionPosition = MathHelper.Clamp(transitionPosition, 0, 1);
                return false;
            }

            // Otherwise we are still busy transitioning.
            return true;
        }

        /// <summary>
        /// Allows the screen to handle user input. Unlike Update, this method
        /// is only called when the screen is active, and not when some other
        /// screen has taken the focus.
        /// </summary>
        public virtual void HandleInput(InputState input) { }

        #endregion

        #region Public Methods

        /// <summary>
        /// Tells the screen to go away. Unlike ScreenManager.RemoveScreen, which
        /// instantly kills the screen, this method respects the transition timings
        /// and will give the screen a chance to gradually transition off.
        /// </summary>
        public void ExitScreen()
        {
            if (this.backgroundScreen != null)
            {
                this.backgroundScreen.ExitScreen();
            }
            if (this.transitionOffTime == TimeSpan.Zero)
            {
                // If the screen has a zero transition time, remove it immediately.
                ScreenManager.RemoveScreen(this);
            }
            else
            {
                // Otherwise flag that it should transition off and then exit.
                isExiting = true;
            }
        }

        #endregion
    }
}
